home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / TP-TSR.ARJ / CMDQ.PAS < prev    next >
Pascal/Delphi Source File  |  1989-02-11  |  27KB  |  585 lines

  1. {═══════════════════════════════ CMDQ.PAS ════════════════════════════════}
  2. { ─────────  Turbo 4.0/5.0 stay-resident demonstration program  ───────── }
  3. {                 Copyright (c) 1989  Richard W. Prescott                 }
  4. { This program provides basic line editing and recall capability at the   }
  5. { DOS command line and within any program that requests keyboard input    }
  6. { through interrupt $21 function $0A (Buffered Input).                    }
  7. {                                                                         }
  8. { The Unit DOS21_0A contains the assembly code for the basic interrupt    }
  9. { routine, which is installed automatically by the "Uses DOS21_0A"        }
  10. { clause.  This routine traps only function $0A (Buffered Input),         }
  11. { chaining to the original interrupt $21 vector for all other function    }
  12. { requests.  The assembly code issues a FAR Call via the Pointer variable }
  13. { PascalCode which is initialized in the MAIN block (below) to point to   }
  14. { the procedure ServiceProc.  ServiceProc repeatedly polls the keyboard   }
  15. { and calls the appropriate Proc/Function to provide the line edit and    }
  16. { recall facilities.                                                      }
  17. {                                                                         }
  18. { The Unit DOS21_0A provides the Procedures IChain for chaining to the    }
  19. { original interrupt routine, and IReturn for returning directly to the   }
  20. { calling program.  These may be called from any point within the Pascal  }
  21. { code.  The user registers at interrupt entry are accessible through the }
  22. { record variable User^ (User^.Ax, User^.Flags, etc).  They should be     }
  23. { modified as necessary to simulate a successful interrupt request before }
  24. { calling IReturn, as illustrated in the procedure ReturnCommand.         }
  25. {                                                                         }
  26. { The Unit CONSOLE provides routines for changing the cursor shape, as    }
  27. { well as substitutes for ReadKey, WhereX/Y, and WRITE.  (The CRT Unit    }
  28. { installs a considerable amount of initialization code, which is         }
  29. { undesirable in a resident program; the CONSOLE Unit installs no         }
  30. { initialization code).  The substitutes for WRITE require less code and  }
  31. { do not respond to Ctrl-C and Ctrl-Break.                                }
  32. {═══════════════════════════════ CMDQ.PAS ════════════════════════════════}
  33.  
  34. {$M $400,0,0} {- INCREASE STACK during program development! -}
  35. {$S-}         {- REMOVE during program development! -}
  36.  
  37. {
  38.    ┌─────────────────────────────────────────────────────────────────┐ 
  39.    │ The default configuration creates a true resident program.      │ 
  40.    │ To create a version which runs a COMMAND.COM Shell, and can be  │ 
  41.    │ removed with the DOS Command "Exit", $Define the conditional    │ 
  42.    │ symbol SHELL or compile using  "TPC cmdq/dshell".  This is      │ 
  43.    │ useful primarily during program development.                    │ 
  44.    └─────────────────────────────────────────────────────────────────┘
  45. }
  46.  
  47. Uses DOS,CONSOLE,DOS21_0A;
  48. CONST
  49.   DefaultMode = TRUE; {Default to Insert}
  50. CONST
  51. {- Standard SCAN Code Constants -}
  52.  
  53.    F1 = $3B;   F2 = $3C;   F3 = $3D;   F4 = $3E;   F5 = $3F;
  54.    F6 = $40;   F7 = $41;   F8 = $42;   F9 = $43;   F0 = $44;
  55.  
  56.    HomeKey       = $47;    CtrlHome      = $77;
  57.    UpArrow       = $48;
  58.    PgUp          = $49;    CtrlPgUp      = $84;
  59.    LeftArrow     = $4B;    CtrlLeftArrow = $73;
  60.    RtArrow       = $4D;    CtrlRtArrow   = $74;
  61.    EndKey        = $4F;    CtrlEnd       = $75;
  62.    DownArrow     = $50;
  63.    PgDn          = $51;    CtrlPgDn      = $76;
  64.    InsertKey     = $52;    DeleteKey     = $53;
  65.  
  66. {- Standard Character Constants -}
  67.  
  68.    CtrlBkSl {^\} = #$1C; 
  69.    BackSpace     = #$08;   CtrlBsp       = #$7F;
  70.    Enter         = #$0D;   CtrlEnter     = #$0A;
  71.    Escape        = #$1B;   Tab           = #$09;
  72.    Null          = #0;
  73.  
  74.  
  75. TYPE
  76.   CmdType = STRING[255];
  77. CONST
  78.   Dormant: BOOLEAN = FALSE;
  79. VAR
  80.   CurrentLine: CmdType;
  81.   CurrentLineLen: BYTE Absolute CurrentLine;
  82.   MaxChars: BYTE; {- Maximum Space for Characters in user buffer -}
  83.   LinePos,SavePos: BYTE;
  84.   InsertMode: BOOLEAN;
  85.  
  86.   CmdQ: ARRAY[0..$FF] OF BYTE;       {- Command Queue -}
  87.   QTail,Qptr,Tptr: ^CmdType;
  88.   QTailLen: ^BYTE Absolute QTail;
  89.   QptrLen:  ^BYTE Absolute QPtr;
  90.   TptrLen:  ^BYTE Absolute TPtr;
  91.   QTailW: WORD Absolute QTail;
  92.   QptrW:  WORD Absolute QPtr;
  93.   TptrW:  WORD Absolute TPtr;
  94.  
  95.   MarkX,MarkY: BYTE;  Mark: WORD Absolute MarkX;  
  96.   Ch: CHAR;    Scan:Byte;    Key: WORD Absolute Ch;
  97.  
  98.  
  99.  
  100. {════════════════════════════════ ReadKey ════════════════════════════════}
  101. { Emulate CRT Unit ReadKey without CRT Unit overhead.  Ignore Ctrl-C and  }
  102. { Ctrl-Break.  Uses DosReadKey OR BiosReadKey from CONSOLE Unit, where    }
  103. { DosReadKey recognizes ANSI macros and BiosReadKey does not.             }
  104. {════════════════════════════════ ReadKey ════════════════════════════════}
  105. FUNCTION ReadKey: CHAR; BEGIN
  106.   ReadKey := DosReadKey;  {- Use BiosReadKey to ignore ANSI Macros -}
  107. END; {FUNCTION ReadKey}
  108.  
  109.  
  110. {══════════════════════════════ ShowCursor ═══════════════════════════════}
  111. { Reset cursor shape based on state of InsertMode flag.                   }
  112. {══════════════════════════════ ShowCursor ═══════════════════════════════}
  113. PROCEDURE ShowCursor; BEGIN
  114.   IF InsertMode THEN WideCursor ELSE ThinCursor;
  115. END; {PROCEDURE ShowCursor}
  116.  
  117.  
  118. {══════════════════════════════ CursorLeft ═══════════════════════════════}
  119. { Move cursor left (or reverse line wrap) and update GLOBAL VAR LinePos.  }
  120. { Cursor is moved by sending a BackSpace (#8), which allows for reverse   }
  121. { line wrap within windows defined under certain BIOS enhancements (e.g.  }
  122. { FANSI-CONSOLE).  If x position does not change, implement reverse line  }
  123. { wrap by decrementing y position and setting x position to the maximum   }
  124. { screen column as determined from the BIOS.                              }
  125. {══════════════════════════════ CursorLeft ═══════════════════════════════}
  126. PROCEDURE CursorLeft; BEGIN
  127.   IF LinePos>1 THEN BEGIN
  128.  
  129.     Mark := ReadCursor;
  130.  
  131.     WriteChar(#8); Dec(LinePos);
  132.  
  133.     IF WhereX = MarkX THEN BEGIN
  134.       Dec(MarkY);  MarkX := MaxColumn;  SetCursor(Mark);
  135.     END; {IF WhereX = MarkX THEN }
  136.  
  137.   END; {IF LinePos>1 THEN }
  138. END; {PROCEDURE CursorLeft}
  139.  
  140.  
  141. {═══════════════════════════════ WordLeft ════════════════════════════════}
  142. { Move cursor to preceding "word start" and update GLOBAL VAR LinePos.    }
  143. { A "word start" is a non-space preceded by a space (or the line start).  }
  144. {═══════════════════════════════ WordLeft ════════════════════════════════}
  145. PROCEDURE WordLeft; BEGIN
  146.   IF LinePos > 1
  147.   THEN REPEAT CursorLeft
  148.        UNTIL (LinePos = 1)
  149.           OR ((CurrentLine[LinePos]<>' ') AND (CurrentLine[LinePos-1]=' '));
  150. END; {PROCEDURE WordLeft}
  151.  
  152.  
  153. {══════════════════════════════ CursorRight ══════════════════════════════}
  154. { Move cursor right (or wrap to next line) and update GLOBAL VAR LinePos. }
  155. { Cursor is moved by writing the character at the current LinePos to the  }
  156. { console, providing automatic line wrap and scrolling as required.       }
  157. {══════════════════════════════ CursorRight ══════════════════════════════}
  158. PROCEDURE CursorRight; BEGIN
  159.   IF LinePos <= CurrentLineLen THEN BEGIN
  160.     WriteChar(CurrentLine[LinePos]); Inc(LinePos);
  161.   END; {IF LinePos>1 THEN }
  162. END; {PROCEDURE CursorRight}
  163.  
  164.  
  165. {═══════════════════════════════ WordRight ═══════════════════════════════}
  166. { Move cursor to following "word start" and update GLOBAL VAR LinePos.    }
  167. { A "word start" is a non-space preceded by a space (or the line end).    }
  168. {═══════════════════════════════ WordRight ═══════════════════════════════}
  169. PROCEDURE WordRight; BEGIN
  170.   IF LinePos <= CurrentLineLen 
  171.   THEN REPEAT CursorRight
  172.        UNTIL (LinePos > CurrentLineLen)
  173.           OR ((CurrentLine[LinePos]<>' ') AND (CurrentLine[LinePos-1]=' '));
  174. END; {PROCEDURE WordRight}
  175.  
  176.  
  177. {═══════════════════════════════ CursorHome ══════════════════════════════}
  178. { Move cursor to the beginning of the line and update GLOBAL VAR LinePos. }
  179. {═══════════════════════════════ CursorHome ══════════════════════════════}
  180. PROCEDURE CursorHome; BEGIN
  181.   WHILE LinePos>1 DO CursorLeft; 
  182. END; {PROCEDURE CursorHome}
  183.  
  184.  
  185. {═══════════════════════════════ CursorEnd ═══════════════════════════════}
  186. { Move cursor to the end of the line and update GLOBAL VAR LinePos.       }
  187. {═══════════════════════════════ CursorEnd ═══════════════════════════════}
  188. PROCEDURE CursorEnd; BEGIN
  189.   WHILE LinePos <= CurrentLineLen DO CursorRight; 
  190. END; {PROCEDURE CursorEnd}
  191.  
  192.  
  193. {═══════════════════════════════ ToggleMode ══════════════════════════════}
  194. { Toggle cursor size and update GLOBAL Flag InsertMode.                   }
  195. {═══════════════════════════════ ToggleMode ══════════════════════════════}
  196. PROCEDURE ToggleMode; BEGIN
  197.   InsertMode := NOT InsertMode;
  198.   ShowCursor;
  199. END; {PROCEDURE ToggleMode}
  200.  
  201.  
  202. {═══════════════════════════════ InsertChar ══════════════════════════════} 
  203. { Insert character at cursor position (moving existing characters and     } 
  204. { cursor one position right) and update GLOBAL VARs CurrentLine and       } 
  205. { LinePos.  Uses SetCursor to restore cursor after screen update.  Note   } 
  206. { however that the last Char written by WriteSubStr may cause the screen  } 
  207. { to scroll, making MarkY invalid.  If WhereY (after update) = MarkY      } 
  208. { (before update) check for scroll by sending a BackSpace; if the cursor  } 
  209. { does not move, a scroll has occurred (decrement MarkY to correct).  If  } 
  210. { it does move, set MarkY = WhereY in case the screen DID scroll but the  } 
  211. { BackSpace caused a reverse line wrap (Supports FANSI-CONSOLE Windows)   } 
  212. {═══════════════════════════════ InsertChar ══════════════════════════════} 
  213. PROCEDURE InsertChar(Ch1: CHAR); VAR Mark2: WORD; BEGIN 
  214.   IF CurrentLineLen < MaxChars-1 THEN BEGIN
  215.     Insert(ch1,CurrentLine,LinePos); CursorRight; { Display Ch/move right }
  216.     Mark := ReadCursor;
  217.     WriteSubStr(CurrentLine,LinePos,1+CurrentLineLen-LinePos);
  218.     IF (LinePos <= CurrentLineLen) AND (WhereY = MarkY) THEN BEGIN
  219.       Mark2 := ReadCursor; WriteChar(#8);        { Send BackSpace }
  220.       IF Mark2 = ReadCursor THEN Dec(MarkY)      { Scrolled: Adjust MarkY }
  221.       ELSE MarkY := WhereY;          { No Scroll or Scroll & reverse wrap }
  222.     END; {IF WhereY = MarkY THEN }
  223.     SetCursor(Mark);
  224.   END; {IF CurrentLineLen < MaxChars-1}
  225. END; {PROCEDURE InsertChar}
  226.  
  227.  
  228. {═══════════════════════════════ OverWrite ═══════════════════════════════}
  229. { Replace character at current cursor position and move right.            }
  230. { Updates GLOBAL VARs CurrentLine and LinePos.                            }
  231. {═══════════════════════════════ OverWrite ═══════════════════════════════} 
  232. PROCEDURE OverWrite(ch1: CHAR); BEGIN
  233.   IF LinePos < MaxChars THEN BEGIN
  234.     IF LinePos > CurrentLineLen THEN Inc(CurrentLineLen);
  235.     WriteChar(Ch1);  CurrentLine[LinePos] := Ch1;  Inc(LinePos);
  236.   END; {IF LinePos < MaxChars}
  237. END; {PROCEDURE OverWrite}
  238.  
  239.  
  240. {═══════════════════════════════ DeleteChar ══════════════════════════════} 
  241. { Delete character at cursor position (moving trailing characters one     } 
  242. { one position left) and update GLOBAL VAR CurrentLine.  Cursor position  }
  243. { is not changed.                                                         }
  244. {═══════════════════════════════ DeleteChar ══════════════════════════════} 
  245. PROCEDURE DeleteChar; BEGIN
  246.   IF LinePos <= CurrentLineLen THEN BEGIN
  247.     Mark := ReadCursor; Delete(CurrentLine,LinePos,1);
  248.     WriteSubStr(CurrentLine,LinePos,1+CurrentLineLen-LinePos);
  249.     WriteChar(' '); SetCursor(Mark);
  250.   END; {IF LinePos <= CurrentLineLen THEN }
  251. END; {PROCEDURE DeleteChar}
  252.  
  253.  
  254. {═══════════════════════════════ DeleteLeft ══════════════════════════════} 
  255. { Delete character to left of cursor (moving existing characters and      } 
  256. { cursor one position left) and update GLOBAL VARs CurrentLine and        } 
  257. { LinePos.                                                                }
  258. {═══════════════════════════════ DeleteLeft ══════════════════════════════} 
  259. PROCEDURE DeleteLeft; BEGIN
  260.   IF LinePos>1 THEN BEGIN
  261.     CursorLeft; DeleteChar;
  262.   END; {IF LinePos>1 THEN }
  263. END; {PROCEDURE DeleteLeft}
  264.  
  265.  
  266. {═══════════════════════════════ DisplayNew ══════════════════════════════} 
  267. { Replace CurrentLine with new command (Cmd), and set LinePos to end of   }
  268. { line.  Erase trailing characters of old line as indicated by OldLen.    }
  269. { Used by EraseLine, DeleteHome, DeleteEnd, PrevCommand, NextCommand,     }
  270. { and ClearCommand.                                                       }
  271. {═══════════════════════════════ DisplayNew ══════════════════════════════} 
  272. PROCEDURE DisplayNew(VAR Cmd: CmdType; OldLen: BYTE); 
  273. VAR n:BYTE;  CmdLen: BYTE Absolute Cmd;  BEGIN
  274.   CursorHome;   
  275.   WriteSubStr(Cmd,1,CmdLen); 
  276.   IF OldLen > CmdLen THEN BEGIN
  277.     Mark := ReadCursor;
  278.     FOR n := CmdLen TO OldLen-1 DO WriteChar(' ');
  279.     SetCursor(Mark);
  280.   END; {IF OldLen > CmdLen THEN }
  281.   CurrentLine := Cmd;  LinePos := CurrentLineLen+1;
  282. END; {PROCEDURE DisplayNew}
  283.  
  284.  
  285. {═══════════════════════════════ EraseLine ═══════════════════════════════} 
  286. { Erase current display line and update GLOBAL VAR CurrentLine.           }
  287. {═══════════════════════════════ EraseLine ═══════════════════════════════} 
  288. PROCEDURE EraseLine; BEGIN
  289.   SavePos := CurrentLineLen;
  290.   CurrentLineLen := 0;
  291.   DisplayNew(CurrentLine,SavePos);
  292. END; {PROCEDURE EraseLine; }
  293.  
  294.  
  295. {═══════════════════════════════ DeleteHome ══════════════════════════════} 
  296. { Delete characters left of cursor and update GLOBAL VAR CurrentLine.     }
  297. { Cursor is placed at the beginning of the new line.                      }
  298. {═══════════════════════════════ DeleteHome ══════════════════════════════} 
  299. PROCEDURE DeleteHome; BEGIN
  300.   IF LinePos>1 THEN BEGIN
  301.     SavePos := CurrentLineLen;
  302.     Delete(CurrentLine,1,LinePos-1);
  303.     DisplayNew(CurrentLine,SavePos);
  304.     CursorHome;
  305.   END; {IF LinePos>1 THEN }
  306. END; {PROCEDURE DeleteHome}
  307.  
  308.  
  309. {═══════════════════════════════ DeleteEnd ═══════════════════════════════} 
  310. { Delete characters from cursor to end of line and update GLOBAL VAR      }
  311. { CurrentLine.  Cursor is left at the end of the line.                    }
  312. {═══════════════════════════════ DeleteEnd ═══════════════════════════════} 
  313. PROCEDURE DeleteEnd; BEGIN
  314.   IF LinePos <= CurrentLineLen THEN BEGIN
  315.     SavePos := CurrentLineLen;
  316.     CurrentLineLen := LinePos-1;
  317.     DisplayNew(CurrentLine,SavePos);
  318.   END; {IF LinePos <= CurrentLineLen THEN }
  319. END; {PROCEDURE DeleteEnd}
  320.  
  321.  
  322.    {══════════════════════════════════════════════════════════════════} 
  323.    { The following five proceduress manipulate the command queue.     }
  324.    { Commands are stored with a leading AND trailing length byte as   }
  325.    { illustrated below:                                               }
  326.    {       [L0]Cmd0[L0] [L1]Cmd1[L1] [L2]Cmd2[L2] [L3][L3]            }
  327.    {        ^Ofs(CmdQ)   ^QPtr                     ^QTail             }
  328.    { QPtr points to the currently displayed command, viewed as a      }
  329.    { String.  QPtrLen points to the same location but refers to the   }
  330.    { length byte only.  It is used to determine the start of the next }
  331.    { command (Length+2 bytes forward).  QPtrW refers to the offset    }
  332.    { portion of the pointer QPtr/QPtrLen.  It is adjusted directly to }
  333.    { change the command referenced by QPtr.  To move backward in the  }
  334.    { queue, QPtrW is decremented so that QPtrLen refers to the        }
  335.    { trailing length byte of the preceding command.  The start of the }
  336.    { command is then Length+1 bytes backward.                         }
  337.    { The oldest command is always at offset 0 within CmdQ, while      }
  338.    { QTail points to the next available location to store a command.  }
  339.    { If there is not sufficient space at QTail to store a new command }
  340.    { the oldest command is discarded and the remaining ones shifted   }
  341.    { left so that the oldest remaining command is again at Ofs(CmdQ). }
  342.    {══════════════════════════════════════════════════════════════════} 
  343.  
  344.  
  345. {══════════════════════════════ NextCommand ══════════════════════════════} 
  346. { Advance QPtr to next command in queue and display it.  If pointer       }
  347. { reaches QTail, cycle back to start of CmdQ (oldest command).            }
  348. {══════════════════════════════ NextCommand ══════════════════════════════} 
  349. PROCEDURE NextCommand; VAR n:BYTE; BEGIN
  350.   IF QTail = @CmdQ THEN Exit;
  351.   IF QPtr = QTail THEN QPtr := @CmdQ
  352.   ELSE Inc(QPtrW, QPtrLen^ + 2);
  353.   IF QPtr = QTail THEN QPtr := @CmdQ;
  354.   DisplayNew(QPtr^,CurrentLineLen);
  355. END; {PROCEDURE NextCommand}
  356.  
  357.  
  358. {══════════════════════════════ PrevCommand ══════════════════════════════}
  359. { If display is blank, display current command at QPtr.  Otherwise move   }
  360. { QPtr back to previous command in queue and display it.  If pointer was  }
  361. { at start of CmdQ (oldest command), cycle to QTail before moving back.   }
  362. {══════════════════════════════ PrevCommand ══════════════════════════════} 
  363. PROCEDURE PrevCommand; BEGIN
  364.   IF QTail = @CmdQ THEN Exit;
  365.  
  366.   IF (QPtr = QTail) OR (CurrentLineLen<>0) THEN BEGIN
  367.     IF Qptr = @CmdQ THEN QPtr := QTail;
  368.     Dec(QptrW); {Now Pointing to length of Prev Command}
  369.     Dec(QptrW, QPtrLen^ + 1);
  370.   END; {IF (QPtr = QTail) OR (CurrentLineLen<>0) THEN }
  371.  
  372.   DisplayNew(QPtr^,CurrentLineLen);
  373.  
  374. END; {PROCEDURE PrevCommand}
  375.  
  376.  
  377. {═════════════════════════════ ClearCommand ══════════════════════════════} 
  378. { Remove currently displayed command from command queue.  Shift remaining }
  379. { commands back to fill the gap, and display the new command at QPtr (the }
  380. { command following the one removed).                                     }
  381. {═════════════════════════════ ClearCommand ══════════════════════════════} 
  382. PROCEDURE ClearCommand; BEGIN
  383.   IF CurrentLine <> QPtr^ THEN BEGIN EraseLine; Exit; END;
  384.   IF (QTail = @CmdQ) OR (QPtr = QTail) THEN Exit;
  385.   Tptr := Qptr;
  386.   Inc(TPtrW, QPtrLen^ + 2);
  387.  
  388.   Move(TPtr^,QPtr^,Ofs(CmdQ)+SizeOf(CmdQ)-TPtrW);
  389.  
  390.   Dec(QTailW,TPtrW-QPtrW);
  391.  
  392.   MemW[Dseg:QTailW]:=0;
  393.   IF QPtr = QTail THEN QPtr := @CmdQ;
  394.   DisplayNew(QPtr^,CurrentLineLen);
  395. END; {PROCEDURE ClearCommand}
  396.  
  397.  
  398. {═══════════════════════════════ ClearQueue ══════════════════════════════}
  399. { Remove all commands from command queue and display a blank line.        } 
  400. {═══════════════════════════════ ClearQueue ══════════════════════════════} 
  401. PROCEDURE ClearQueue; BEGIN
  402.   EraseLine;
  403.   Qtail:=@CmdQ; QPtr:=QTail; MemW[Dseg:Ofs(CmdQ)]:=0;
  404. END; {PROCEDURE ClearQueue}
  405.  
  406.  
  407. {══════════════════════════════ QueueCommand ═════════════════════════════}
  408. { Append currently displayed command to command queue.  If sufficient     }
  409. { space is not available at QTail, discard oldest command(s) and move     }
  410. { remaining commands back until oldest remaining command is at Ofs(CmdQ). }
  411. {══════════════════════════════ QueueCommand ═════════════════════════════}
  412. PROCEDURE QueueCommand; BEGIN
  413.   TPtr := @CmdQ;
  414.   WHILE CurrentLineLen+2+QTailW-TPtrW > SizeOf(CmdQ) 
  415.   DO Inc(TPtrW, TPtrLen^ + 2);
  416.   IF   TPtrW <> Ofs(CmdQ) 
  417.   THEN Move(TPtr^,CmdQ,Ofs(CmdQ)+SizeOf(CmdQ)-TPtrW);
  418.   Dec(QTailW,TPtrW-Ofs(CmdQ));
  419.  
  420.   QTail^ := CurrentLine;            {- Add command string -}
  421.   Inc(QTailW,CurrentLineLen+1);
  422.   QTailLen^ := CurrentLineLen;      {- Add trailing length byte -}
  423.   Inc(QTailW);                      {- Set new QTail -}
  424.   QPtr := QTail;                    {- Set Qptr to new QTail -}
  425. END; {PROCEDURE QueueCommand}
  426.  
  427.  
  428. {═════════════════════════════ ReturnCommand ═════════════════════════════}
  429. { Execute return from interrupt.  Place currently displayed command       }
  430. { STRING (including Length byte) at offset 1 within callers buffer at     }
  431. { Ds:Dx, and add trailing Carriage Return (#13, not counted in length).   }
  432. { This emulates the documented action of Interrupt $21 function $0A:      }
  433. {     Input Buffer:   [BufferSize][Length][Line Returned][#13]            }
  434. {    Caller's Ds:Dx    ^+0         ^+1     ^+2            ^+Length+2      }
  435. { The Buffer Size at Ds:Dx is supplied by the caller.  It is read into    }
  436. { MaxChars (below) and used by InsertChar and OverWrite to limit the      }
  437. { maximum allowable size of CommandLine.                                  }
  438. {═════════════════════════════ ReturnCommand ═════════════════════════════}
  439. PROCEDURE ReturnCommand; BEGIN
  440.   CurrentLine[CurrentLineLen+1] := #13;
  441.   Move(CurrentLine,Mem[User^.Ds:User^.Dx +1],CurrentLineLen+2);
  442.   CursorEnd;             {- for wrapped lines -}
  443.   ShowCursor;            {- during command execution -}
  444.   Dos21_0A.IReturn;
  445. END; {PROCEDURE ReturnCommand}
  446.  
  447.  
  448. {══════════════════════════════ QueueReturn ══════════════════════════════}
  449. { Return Command, adding it to the command queue if new or modified.      }
  450. { Short commands are not added to the queue.                              }
  451. {══════════════════════════════ QueueReturn ══════════════════════════════}
  452. PROCEDURE QueueReturn; BEGIN
  453.   IF (CurrentLineLen > 2) 
  454.   AND (CurrentLine <> QPtr^) 
  455.   THEN QueueCommand;
  456.   ReturnCommand;
  457. END; {PROCEDURE QueueReturn}
  458.  
  459.  
  460. {══════════════════════════════ MacroReturn ══════════════════════════════} 
  461. { Return a predefined command if one is defined for the Scan code of the  }
  462. { key pressed.  Otherwise exit with no action.  Macro commands are not    }
  463. { added to the queue.  This feature may be removed or expanded as desired }
  464. {══════════════════════════════ MacroReturn ══════════════════════════════} 
  465. PROCEDURE MacroReturn; BEGIN
  466.   SavePos := CurrentLineLen; 
  467.   CASE Scan OF
  468.     F1: CurrentLine := 'exit';
  469.     F5: CurrentLine := 'dir c:';
  470.    else Exit;
  471.   END; {CASE Scan}
  472.   DisplayNew(CurrentLine,SavePos); 
  473.   ReturnCommand;  {- Return Command without adding to queue -}
  474. END; {PROCEDURE MacroReturn; 
  475.  
  476.  
  477. {══════════════════════════════ DisplayPath ══════════════════════════════} 
  478. { Display current directory if caller is COMMAND.COM and default drive    }
  479. { is C or higher.                                                         }
  480. {══════════════════════════════ DisplayPath ══════════════════════════════} 
  481. PROCEDURE DisplayPath; VAR Directory: STRING[67];  BEGIN
  482.   IF (DefaultDrive >= 'C') AND (User^.Ds = CommandSig) 
  483.    AND (WhereX = 3) THEN BEGIN
  484.     GetDir(0,Directory);
  485.     WriteChar(#8);    WriteChar(#8);
  486.     WriteSubStr(Directory,1,Length(Directory));
  487.     WriteChar('>');
  488.   END; {IF DefaultDrive >= 'C' THEN }
  489. END; {PROCEDURE DisplayPath; }
  490.  
  491.  
  492. {══════════════════════════════ ServiceProc ══════════════════════════════} 
  493. { This is the Pascal code for the interrupt service routine, called from  }
  494. { DOS21_0A.IHook.  If Dormant, checks FIRST keystroke of each line        }
  495. { requested for the wakeup combination Ctrl-\.  If active, initialize     }
  496. { CurrentLine and cursor shape, read Caller's buffer size into MaxChars,  }
  497. { and display current directory path (except floppy drives).  Then poll   }
  498. { the keyboard and execute edit requests until carriage return or macro.  }
  499. { If Ctrl-\ is pressed while active, set Dormant flag and chain to the    }
  500. { original interrupt service routine.                                     }
  501. {══════════════════════════════ ServiceProc ══════════════════════════════} 
  502. {$F+} PROCEDURE ServiceProc; {$F-}               {- Force FAR Return -}
  503. {- The Pascal code for the Interrupt Service must be a FAR Procedure -}
  504. BEGIN
  505.  
  506.   IF Dormant THEN BEGIN
  507.     Key := LookAhead; {- Inspect Key but leave in buffer -}
  508.     IF Ch = CtrlBkSl 
  509.     THEN BEGIN Dormant := FALSE; Ch := ReadKey; END
  510.     ELSE Dos21_0A.IChain;
  511.   END; {IF Dormant THEN }
  512.  
  513.   LinePos := 1;  CurrentLineLen := 0;
  514.   InsertMode := DefaultMode;  ShowCursor;  {- set default -}
  515.   MaxChars := Mem[User^.Ds:User^.Dx];
  516.   DisplayPath;
  517.  
  518. REPEAT 
  519.    {- Display cursor during wait for keystroke -}
  520.   ShowCursor;       Ch := ReadKey;      HideCursor;
  521.   CASE Ch OF
  522.     CtrlBkSl:   BEGIN Dormant := TRUE; EraseLine; 
  523.                       ShowCursor;      Dos21_0A.IChain; 
  524.                 END;
  525.     Enter:      QueueReturn;
  526.     Escape:     EraseLine;
  527.     BackSpace:  DeleteLeft;
  528.  
  529.     #32..#255:  {- Printable Character -}
  530.                 IF InsertMode THEN InsertChar(ch) ELSE OverWrite(ch);
  531.  
  532.     Null: BEGIN {- Extended Key -}
  533.       ShowCursor;       Scan := Byte(ReadKey);      HideCursor;
  534.       CASE Scan OF
  535.         LeftArrow:      CursorLeft;          RtArrow:        CursorRight;
  536.         CtrlLeftArrow:  WordLeft;            CtrlRtArrow:    WordRight;
  537.         HomeKey:        CursorHome;          EndKey:         CursorEnd;
  538.         CtrlHome:       DeleteHome;          CtrlEnd:        DeleteEnd;
  539.         DeleteKey:      DeleteChar;          InsertKey:      ToggleMode;
  540.         UpArrow:        PrevCommand;         DownArrow:      NextCommand;
  541.         CtrlPgDn:       ClearCommand;        CtrlPgUp:       ClearQueue;
  542.         else            MacroReturn;
  543.       END; {CASE Scan }
  544.     END; {Null: }
  545.   END; {CASE Ch}
  546. UNTIL FALSE;
  547. END; {PROCEDURE ServiceProc}
  548.  
  549.  
  550. {═════════════════════════════════ Shell ═════════════════════════════════}
  551. { Set Sp for Exec Call to avoid our interrupt service stack, then Exec    }
  552. { COMMAND.COM, looking first on Drive C and then on Drive A.  One could   }
  553. { also scan the environment block to find the current COMSPEC (even       }
  554. { though the memory block has been released), but the present method is   }
  555. { considerably simpler.  On return from Exec, restore original interrupt. }
  556. {═════════════════════════════════ Shell ═════════════════════════════════}
  557. {$IFDEF Shell}    {- Avoid unneeded data ErrMsg IFNDEF Shell -}
  558. PROCEDURE Shell; 
  559.   CONST ErrMsg: STRING[25] = 'A:\COMMAND.COM Not Found'#10; 
  560. BEGIN
  561. {- Set Sp low to insure that "resident" stack doesn't overlay Exec Return -}
  562.   SetSpLow;
  563.   Exec('C:\Command.com','');
  564.   IF DosError <> 0 THEN Exec('A:\Command.com','');
  565.   IF DosError <> 0 THEN WriteSubStr(ErrMsg,1,Length(ErrMsg));
  566.   Dos21_0A.Irestore;
  567. {- NOTE that Sp is restored by the standard PROCEDURE exit code -}  
  568. END; {PROCEDURE Shell; }
  569. {$ENDIF}
  570.  
  571.  
  572. {══════════════════════════════════ MAIN ═════════════════════════════════}
  573. { Initialize Command Queue and set PascalCode Pointer to @ServiceProc.    }
  574. { Release unneeded environment block, then Shell or go resident.          }
  575. {══════════════════════════════════ MAIN ═════════════════════════════════}
  576. BEGIN {- MAIN PROGRAM SETUP -}
  577.  
  578.   Qtail:=@CmdQ; QPtr:=QTail; MemW[Dseg:Ofs(CmdQ)]:=0;
  579.   Dos21_0A.PascalCode := @ServiceProc;
  580.   FreeEnvironmentBlock;
  581.  
  582.   {$IFDEF Shell}   Shell;   {$ELSE}   Keep(0);   {$ENDIF}
  583.  
  584. END.
  585.